﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Indexes;
using EffiProz.Core.Errors;
using EffiProz.Core.Persist;

namespace EffiProz.Core
{
    public class ParserBase
    {
        private Scanner scanner;
        public Token token;

        //
        protected bool isRecording;
        protected EfzArrayList recordedStatement;

        //
        public bool isCheckOrTriggerCondition;
        public bool isSchemaDefinition;
        protected int parsePosition;
        public const decimal LONG_MAX_VALUE_INCREMENT = (decimal)long.MaxValue + (decimal)1;


        /**
         * Constructs a new BaseParser object with the given context.
         *
         * @param t the token source from which to parse commands
         */
        public ParserBase(Scanner t)
        {
            scanner = t;
            token = scanner.token;
        }

        public Scanner getScanner()
        {
            return scanner;
        }

        public int getParsePosition()
        {
            return parsePosition;
        }

        public void setParsePosition(int parsePosition)
        {
            this.parsePosition = parsePosition;
        }

        /**
         *  Resets this parse context with the given SQL character sequence.
         *
         * Internal structures are reset as though a new parser were created
         * with the given sql and the originally specified database and session
         *
         * @param sql a new SQL character sequence to replace the current one
         */
        public virtual void reset(String sql)
        {

            scanner.reset(sql);

            //
            parsePosition = 0;
            isCheckOrTriggerCondition = false;
            isSchemaDefinition = false;
            isRecording = false;
            recordedStatement = null;
        }

        public int getPosition()
        {
            return scanner.getTokenPosition();
        }

        public void rewind(int position)
        {

            if (position == scanner.getTokenPosition())
            {
                return;
            }

            scanner.position(position);

            if (isRecording)
            {
                int i = recordedStatement.size() - 1;

                for (; i >= 0; i--)
                {
                    Token token = (Token)recordedStatement.get(i);

                    if (token.position < position)
                    {
                        break;
                    }
                }

                recordedStatement.setSize(i + 1);
            }

            read();
        }

        public String getLastPart()
        {
            return scanner.getPart(parsePosition, scanner.getTokenPosition());
        }

        public String getLastPart(int position)
        {
            return scanner.getPart(position, scanner.getTokenPosition());
        }

        public String getLastPartAndCurrent(int position)
        {
            return scanner.getPart(position, scanner.getPosition());
        }

        public String getStatement(int startPosition, short[] startTokens)
        {

            int semiPosition = 0;

            while (true)
            {
                if (token.tokenType == Tokens.SEMICOLON)
                {
                    semiPosition = scanner.getPosition();
                }
                else if (token.tokenType == Tokens.X_ENDPARSE)
                {
                    if (semiPosition == 0)
                    {
                        break;
                    }
                    else
                    {
                        rewind(semiPosition);

                        break;
                    }
                }
                else
                {
                    semiPosition = 0;

                    if (ArrayUtil.find(startTokens, token.tokenType) != -1)
                    {
                        break;
                    }
                }

                read();
            }

            String sql = scanner.getPart(startPosition, scanner.getPosition());

            return sql;
        }

        //
        public void startRecording()
        {

            recordedStatement = new EfzArrayList();

            recordedStatement.add(token.duplicate());

            isRecording = true;
        }

        public void recordExpressionForToken(ExpressionColumn expression)
        {

            if (isRecording)
            {
                Token recordToken =
                    (Token)recordedStatement.get(recordedStatement.size() - 1);

                recordToken.columnExpression = expression;
            }
        }

        public Token[] getRecordedStatement()
        {

            isRecording = false;

            recordedStatement.remove(recordedStatement.size() - 1);

            Token[] tokens = new Token[recordedStatement.size()];

            recordedStatement.toArray(tokens);

            recordedStatement = null;

            return tokens;
        }

        public void read()
        {

            scanner.scanNext();

            if (token.isMalformed)
            {
                int errorCode = -1;

                switch (token.tokenType)
                {

                    case Tokens.X_MALFORMED_BINARY_STRING:
                        errorCode = ErrorCode.X_42587;
                        break;

                    case Tokens.X_MALFORMED_BIT_STRING:
                        errorCode = ErrorCode.X_42588;
                        break;

                    case Tokens.X_MALFORMED_UNICODE_STRING:
                        errorCode = ErrorCode.X_42586;
                        break;

                    case Tokens.X_MALFORMED_STRING:
                        errorCode = ErrorCode.X_42584;
                        break;

                    case Tokens.X_UNKNOWN_TOKEN:
                        errorCode = ErrorCode.X_42582;
                        break;

                    case Tokens.X_MALFORMED_NUMERIC:
                        errorCode = ErrorCode.X_42585;
                        break;

                    case Tokens.X_MALFORMED_COMMENT:
                        errorCode = ErrorCode.X_42589;
                        break;

                    case Tokens.X_MALFORMED_IDENTIFIER:
                        errorCode = ErrorCode.X_42583;
                        break;
                }

                throw Error.error(errorCode);
            }

            if (isRecording)
            {
                Token dup = token.duplicate();

                dup.position = scanner.getTokenPosition();

                recordedStatement.add(dup);
            }
        }

        public bool isReservedKey()
        {
            return scanner.token.isReservedIdentifier;
        }

        public bool isCoreReservedKey()
        {
            return scanner.token.isCoreReservedIdentifier;
        }

        public bool isNonReservedIdentifier()
        {

            return !scanner.token.isReservedIdentifier
                   && (scanner.token.isUndelimitedIdentifier
                       || scanner.token.isDelimitedIdentifier);
        }

        public void checkIsNonReservedIdentifier()
        {

            if (!isNonReservedIdentifier())
            {
                throw unexpectedToken();
            }
        }

        public bool isNonCoreReservedIdentifier()
        {

            return !scanner.token.isCoreReservedIdentifier
                   && (scanner.token.isUndelimitedIdentifier
                       || scanner.token.isDelimitedIdentifier);
        }

        public void checkIsNonCoreReservedIdentifier()
        {

            if (!isNonCoreReservedIdentifier())
            {
                throw unexpectedToken();
            }
        }

        public bool isIdentifier()
        {
            return scanner.token.isUndelimitedIdentifier
                   || scanner.token.isDelimitedIdentifier;
        }

        public void checkIsIdentifier()
        {

            if (!isIdentifier())
            {
                throw unexpectedToken();
            }
        }

        public bool isDelimitedIdentifier()
        {
            return scanner.token.isDelimitedIdentifier;
        }

        public void checkIsDelimitedIdentifier()
        {

            if (token.tokenType != Tokens.X_DELIMITED_IDENTIFIER)
            {
                throw Error.error(ErrorCode.X_42569);
            }
        }

        public void checkIsNotQuoted()
        {

            if (token.tokenType == Tokens.X_DELIMITED_IDENTIFIER)
            {
                throw unexpectedToken();
            }
        }

        public void checkIsValue()
        {

            if (token.tokenType != Tokens.X_VALUE)
            {
                throw unexpectedToken();
            }
        }

        public void checkIsValue(int dataTypeCode)
        {

            if (token.tokenType != Tokens.X_VALUE
                    || token.dataType.typeCode != dataTypeCode)
            {
                throw unexpectedToken();
            }
        }

        public void checkIsThis(int type)
        {

            if (token.tokenType != type)
            {
                throw unexpectedToken();
            }
        }

        public bool isUndelimitedSimpleName()
        {
            return token.isUndelimitedIdentifier && token.namePrefix == null;
        }

        public bool isDelimitedSimpleName()
        {
            return token.isDelimitedIdentifier && token.namePrefix == null;
        }

        public bool isSimpleName()
        {
            return isNonCoreReservedIdentifier() && token.namePrefix == null;
        }

        public void checkIsSimpleName()
        {

            if (!isSimpleName())
            {
                throw unexpectedToken();
            }
        }

        public String readQuotedString()
        {

            if (token.dataType.typeCode != Types.SQL_CHAR)
            {
                throw Error.error(ErrorCode.X_42565);
            }

            String value = token.tokenString;

            read();

            return value;
        }

        public void readThis(int tokenId)
        {

            if (token.tokenType != tokenId)
            {
                String required = Tokens.getKeyword(tokenId);

                throw unexpectedTokenRequire(required);
            }

            read();
        }

        public bool readIfThis(int tokenId)
        {

            if (token.tokenType == tokenId)
            {
                read();

                return true;
            }

            return false;
        }

        public int readIntegerObject()
        {

            int value = readInteger();

            return (value);
        }

        public int readInteger()
        {

            bool minus = false;

            if (token.tokenType == Tokens.MINUS)
            {
                minus = true;

                read();
            }

            checkIsValue();

            if (minus && token.dataType.typeCode == Types.SQL_BIGINT
                    && (Convert.ToInt64(token.tokenValue))
                       == -(long)int.MinValue)
            {
                read();

                return int.MinValue;
            }

            if (token.dataType.typeCode != Types.SQL_INTEGER)
            {
                throw Error.error(ErrorCode.X_42565);
            }

            int val = Convert.ToInt32(token.tokenValue);

            if (minus)
            {
                val = -val;
            }

            read();

            return val;
        }

        public long readBigint()
        {

            bool minus = false;

            if (token.tokenType == Tokens.MINUS)
            {
                minus = true;

                read();
            }

            checkIsValue();

            if (minus && token.dataType.typeCode == Types.SQL_NUMERIC
                    && LONG_MAX_VALUE_INCREMENT.Equals(token.tokenValue))
            {
                read();

                return long.MinValue;
            }

            if (token.dataType.typeCode != Types.SQL_INTEGER
                    && token.dataType.typeCode != Types.SQL_BIGINT)
            {
                throw Error.error(ErrorCode.X_42565);
            }

            long val = Convert.ToInt64(token.tokenValue);

            if (minus)
            {
                val = -val;
            }

            read();

            return val;
        }

        public Expression readDateTimeIntervalLiteral()
        {

            int pos = getPosition();

            switch (token.tokenType)
            {

                case Tokens.DATE:
                    {
                        read();

                        if (token.tokenType != Tokens.X_VALUE
                                || token.dataType.typeCode != Types.SQL_CHAR)
                        {
                            break;
                        }

                        String s = token.tokenString;

                        read();

                        Object date = scanner.newDate(s);

                        return new ExpressionValue(date, SqlType.SQL_DATE);
                    }
                case Tokens.TIME:
                    {
                        read();

                        if (token.tokenType != Tokens.X_VALUE
                                || token.dataType.typeCode != Types.SQL_CHAR)
                        {
                            break;
                        }

                        String s = token.tokenString;

                        read();

                        TimeData value = scanner.newTime(s);
                        SqlType dataType = scanner.dateTimeType;

                        return new ExpressionValue(value, dataType);
                    }
                case Tokens.TIMESTAMP:
                    {
                        read();

                        if (token.tokenType != Tokens.X_VALUE
                                || token.dataType.typeCode != Types.SQL_CHAR)
                        {
                            break;
                        }

                        String s = token.tokenString;

                        read();

                        Object date = scanner.newTimestamp(s);
                        SqlType dataType = scanner.dateTimeType;

                        return new ExpressionValue(date, dataType);
                    }
                case Tokens.INTERVAL:
                    {
                        bool minus = false;

                        read();

                        if (token.tokenType == Tokens.MINUS)
                        {
                            read();

                            minus = true;
                        }
                        else if (token.tokenType == Tokens.PLUS)
                        {
                            read();
                        }

                        if (token.tokenType != Tokens.X_VALUE
                                || token.dataType.typeCode != Types.SQL_CHAR)
                        {
                            break;
                        }

                        String s = token.tokenString;

                        read();

                        IntervalType dataType = readIntervalType(false);
                        Object interval = scanner.newInterval(s, dataType);

                        dataType = (IntervalType)scanner.dateTimeType;

                        if (minus)
                        {
                            interval = dataType.negate(interval);
                        }

                        return new ExpressionValue(interval, dataType);
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "ParserBase");
            }

            rewind(pos);

            return null;
        }

        public IntervalType readIntervalType(bool maxPrecisionDefault)
        {

            int precision = maxPrecisionDefault ? IntervalType.maxIntervalPrecision
                                                : -1;
            int scale = -1;
            int startToken;
            int endToken;

            startToken = endToken = token.tokenType;

            read();

            if (token.tokenType == Tokens.OPENBRACKET)
            {
                read();

                precision = readInteger();

                if (precision <= 0)
                {
                    throw Error.error(ErrorCode.X_42592);
                }

                if (token.tokenType == Tokens.COMMA)
                {
                    if (startToken != Tokens.SECOND)
                    {
                        throw unexpectedToken();
                    }

                    read();

                    scale = readInteger();

                    if (scale < 0)
                    {
                        throw Error.error(ErrorCode.X_42592);
                    }
                }

                readThis(Tokens.CLOSEBRACKET);
            }

            if (token.tokenType == Tokens.TO)
            {
                read();

                endToken = token.tokenType;

                read();
            }

            if (token.tokenType == Tokens.OPENBRACKET)
            {
                if (endToken != Tokens.SECOND || endToken == startToken)
                {
                    throw unexpectedToken();
                }

                read();

                scale = readInteger();

                if (scale < 0)
                {
                    throw Error.error(ErrorCode.X_42592);
                }

                readThis(Tokens.CLOSEBRACKET);
            }

            int startIndex = ArrayUtil.find(Tokens.SQL_INTERVAL_FIELD_CODES,
                                            startToken);
            int endIndex = ArrayUtil.find(Tokens.SQL_INTERVAL_FIELD_CODES,
                                          endToken);

            return IntervalType.getIntervalType(startIndex, endIndex, precision,
                                                scale);
        }

        public static int getExpressionType(int tokenT)
        {

            int type = expressionTypeMap.get(tokenT, -1);

            if (type == -1)
            {
                throw Error.runtimeError(ErrorCode.U_S0500, "ParserBase");
            }

            return type;
        }

        private static IntKeyIntValueHashMap expressionTypeMap =
            new IntKeyIntValueHashMap(37);

        static ParserBase()
        {

            // comparison
            expressionTypeMap.put(Tokens.EQUALS, OpTypes.EQUAL);
            expressionTypeMap.put(Tokens.GREATER, OpTypes.GREATER);
            expressionTypeMap.put(Tokens.LESS, OpTypes.SMALLER);
            expressionTypeMap.put(Tokens.GREATER_EQUALS, OpTypes.GREATER_EQUAL);
            expressionTypeMap.put(Tokens.LESS_EQUALS, OpTypes.SMALLER_EQUAL);
            expressionTypeMap.put(Tokens.NOT_EQUALS, OpTypes.NOT_EQUAL);

            // aggregates
            expressionTypeMap.put(Tokens.COUNT, OpTypes.COUNT);
            expressionTypeMap.put(Tokens.MAX, OpTypes.MAX);
            expressionTypeMap.put(Tokens.MIN, OpTypes.MIN);
            expressionTypeMap.put(Tokens.SUM, OpTypes.SUM);
            expressionTypeMap.put(Tokens.AVG, OpTypes.AVG);
            expressionTypeMap.put(Tokens.EVERY, OpTypes.EVERY);
            expressionTypeMap.put(Tokens.ANY, OpTypes.SOME);
            expressionTypeMap.put(Tokens.SOME, OpTypes.SOME);
            expressionTypeMap.put(Tokens.STDDEV_POP, OpTypes.STDDEV_POP);
            expressionTypeMap.put(Tokens.STDDEV_SAMP, OpTypes.STDDEV_SAMP);
            expressionTypeMap.put(Tokens.VAR_POP, OpTypes.VAR_POP);
            expressionTypeMap.put(Tokens.VAR_SAMP, OpTypes.VAR_SAMP);
        }

       public CoreException unexpectedToken(String tokenS)
        {
            return Error.error(ErrorCode.X_42581, tokenS);
        }

       public CoreException unexpectedTokenRequire(String required)
        {

            if (token.tokenType == Tokens.X_ENDPARSE)
            {
                return Error.error(null, ErrorCode.X_42590,
                                   ErrorCode.TOKEN_REQUIRED, new Object[] {
                "", required
            });
            }

            String tokenS;

            if (token.charsetSchema != null)
            {
                tokenS = token.charsetSchema;
            }
            else if (token.charsetName != null)
            {
                tokenS = token.charsetName;
            }
            else if (token.namePrePrefix != null)
            {
                tokenS = token.namePrePrefix;
            }
            else if (token.namePrefix != null)
            {
                tokenS = token.namePrefix;
            }
            else
            {
                tokenS = token.tokenString;
            }

            return Error.error(null, ErrorCode.X_42581, ErrorCode.TOKEN_REQUIRED,
                               new Object[] {
            tokenS, required
        });
        }

       public CoreException unexpectedToken()
        {

            if (token.tokenType == Tokens.X_ENDPARSE)
            {
                return Error.error(ErrorCode.X_42590);
            }

            String tokenS;

            if (token.charsetSchema != null)
            {
                tokenS = token.charsetSchema;
            }
            else if (token.charsetName != null)
            {
                tokenS = token.charsetName;
            }
            else if (token.namePrePrefix != null)
            {
                tokenS = token.namePrePrefix;
            }
            else if (token.namePrefix != null)
            {
                tokenS = token.namePrefix;
            }
            else
            {
                tokenS = token.tokenString;
            }

            return Error.error(ErrorCode.X_42581, tokenS);
        }

       public CoreException tooManyIdentifiers()
        {

            String tokenS;

            if (token.namePrePrePrefix != null)
            {
                tokenS = token.namePrePrePrefix;
            }
            else if (token.namePrePrefix != null)
            {
                tokenS = token.namePrePrefix;
            }
            else if (token.namePrefix != null)
            {
                tokenS = token.namePrefix;
            }
            else
            {
                tokenS = token.tokenString;
            }

            return Error.error(ErrorCode.X_42551, tokenS);
        }

        public CoreException unsupportedFeature()
        {
            return Error.error(ErrorCode.X_0A501, token.tokenString);
        }

       public CoreException unsupportedFeature(String str)
        {
            return Error.error(ErrorCode.X_0A501, str);
        }

        public ValueType convertToNumber(String s, NumberType type)
        {
            return scanner.convertToNumber(s, type);
        }
    }
}
